﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Pixysoft.SQL
{
    public sealed class SqlTokenizer
    {
        private string sCommand;
        private char[] cCommand;
        private int iLength;
        private int iIndex;
        private TokenType iType;
        private string sToken, sLongNameFirst, sLongNameLast;
        private bool bWait;
        private static List<string> hKeyword;

        string[] keyword = 
		{
			"AND", "ALL", "AVG", "BY", "BETWEEN", "COUNT", "CASEWHEN",
			"DISTINCT", "EXISTS", "EXCEPT", "FALSE", "FROM",
			"GROUP", "IF", "INTO", "IFNULL", "IS", "IN", "INTERSECT", "INNER",
			"LEFT", "LIKE", "MAX", "MIN", "NULL", "NOT", "ON", "ORDER", "OR",
			"OUTER", "PRIMARY", "SELECT", "SET", "SUM", "TO", "TRUE",
			"UNIQUE", "UNION", "VALUES", "WHERE", "CONVERT", "CAST",
			"CONCAT", "MINUS", "CALL"
		};


        public SqlTokenizer(string sql)
        {
            if (hKeyword == null)
            {
                hKeyword = new List<string>();
                for (int i = 0; i < keyword.Length; i++)
                {
                    hKeyword.Add(keyword[i]);
                }
            }

            sCommand = sql;
            cCommand = sql.ToCharArray();
            iLength = cCommand.Length;
            iIndex = 0;
        }

        /// <summary>
        /// 停止前进一次
        /// </summary>
        public void Back()
        {
            bWait = true;
        }

        /// <summary>
        /// 取得下一个字段 如果back则返回当前字段
        /// </summary>
        /// <returns></returns>
        public bool Next()
        {
            if (bWait)
            {
                bWait = false;

                return true;
            }

            while (iIndex < iLength && Char.IsWhiteSpace(cCommand[iIndex]))
            {
                iIndex++;
            }

            sToken = "";

            if (iIndex >= iLength)
            {
                iType = 0;

                return false;
            }

            bool point = false, digit = false, exp = false, afterexp = false;
            bool end = false;
            char c = cCommand[iIndex];
            char cfirst = '0';
            StringBuilder name = new StringBuilder();

            if (Char.IsLetter(c))
            {
                iType = TokenType.NAME;
            }
            else if ("(),*=;+%".IndexOf(c) >= 0)
            {
                iType = TokenType.SPECIAL;
                iIndex++;
                sToken = "" + c;

                return true;
            }
            else if (Char.IsDigit(c))
            {
                iType = TokenType.NUMBER;
                digit = true;
            }
            else if (":".IndexOf(c) >= 0)
            {
                cfirst = c;
                iType = TokenType.VARIABLE;
            }
            else if ("!<>|/-".IndexOf(c) >= 0)
            {
                cfirst = c;
                iType = TokenType.SPECIAL;
            }
            else if (c == '\"')
            {
                iType = TokenType.QUOTED_IDENTIFIER;

                name.Append('\"');
            }
            else if (c == '\'')
            {
                iType = TokenType.STRING;

                name.Append('\'');
            }
            else if (c == '.')
            {
                iType = TokenType.FLOAT;
                point = true;
            }
            else
            {
                throw new Exception("unexpected token. " + c);
            }

            int start = iIndex++;

            while (true)
            {
                if (iIndex >= iLength)
                {
                    c = ' ';
                    end = true;
                }
                else
                {
                    c = cCommand[iIndex];
                }

                switch (iType)
                {
                    case TokenType.NAME:
                        if (Char.IsLetter(c) || Char.IsDigit(c) || c.Equals('_'))
                        {
                            break;
                        }

                        sToken = sCommand.Substring(start, (iIndex - start)).ToUpper();

                        if (c == '.')
                        {
                            sLongNameFirst = sToken;
                            iIndex++;

                            Next();	       // todo: eliminate recursion

                            sLongNameLast = sToken;
                            iType = TokenType.LONG_NAME;
                            sToken = sLongNameFirst + "." + sLongNameLast;
                        }

                        return true;

                    case TokenType.QUOTED_IDENTIFIER:
                        if (c == '\"')
                        {
                            //10-02-14 添加反括号

                            name.Append(c);

                            iIndex++;

                            if (iIndex >= iLength)
                            {
                                sToken = name.ToString();

                                return true;
                            }

                            c = cCommand[iIndex];

                            if (c == '.')
                            {
                                sLongNameFirst = name.ToString();
                                iIndex++;

                                Next();    // todo: eliminate recursion

                                sLongNameLast = sToken;
                                iType = TokenType.LONG_NAME;
                                sToken = sLongNameFirst + "." + sLongNameLast;

                                return true;
                            }

                            if (c != '\"')
                            {
                                sToken = name.ToString();

                                return true;
                            }
                        }

                        name.Append(c);

                        break;

                    case TokenType.VARIABLE:
                        if (char.IsWhiteSpace(c) ||
                           char.IsPunctuation(c) ||
                           char.IsSeparator(c) ||
                           iIndex >= iLength)
                        {
                            sToken = name.ToString();

                            return true;
                        }
                        name.Append(c);

                        break;
                    case TokenType.STRING:
                        if (c == '\'')
                        {
                            iIndex++;

                            if (iIndex >= iLength || cCommand[iIndex] != '\'')
                            {
                                //10-02-14 添加反括号

                                name.Append(c);

                                sToken = name.ToString();

                                return true;
                            }
                        }

                        name.Append(c);

                        break;

                    case TokenType.REMARK:
                        if (end)
                        {

                            // unfinished remark
                            // maybe print error here
                            iType = 0;

                            return true;
                        }
                        else if (c == '*')
                        {
                            iIndex++;

                            if (iIndex < iLength && cCommand[iIndex] == '/')
                            {

                                // using recursion here
                                iIndex++;

                                Next();

                                return true;
                            }
                        }

                        break;

                    case TokenType.REMARK_LINE:
                        if (end)
                        {
                            iType = 0;

                            return true;
                        }
                        else if (c == '\r' || c == '\n')
                        {

                            // using recursion here
                            Next();

                            return true;
                        }

                        break;

                    case TokenType.SPECIAL:
                        if (c == '/' && cfirst == '/')
                        {
                            iType = TokenType.REMARK_LINE;

                            break;
                        }
                        else if (c == '-' && cfirst == '-')
                        {
                            iType = TokenType.REMARK_LINE;

                            break;
                        }
                        else if (c == '*' && cfirst == '/')
                        {
                            iType = TokenType.REMARK;

                            break;
                        }
                        else if (">=|".IndexOf(c) >= 0)
                        {
                            break;
                        }

                        sToken = sCommand.Substring(start, (iIndex - start));

                        return true;

                    case TokenType.FLOAT:
                    case TokenType.NUMBER:
                        if (Char.IsDigit(c))
                        {
                            digit = true;
                        }
                        else if (c == '.')
                        {
                            iType = TokenType.FLOAT;

                            if (point)
                            {
                                throw new Exception("unexpected token. '.'");
                            }

                            point = true;
                        }
                        else if (c == 'E' || c == 'e')
                        {
                            if (exp)
                            {
                                throw new Exception("unexpected token. 'E'");
                            }

                            afterexp = true;    // first character after exp may be + or -
                            point = true;
                            exp = true;
                        }
                        else if (c == '-' && afterexp)
                        {
                            afterexp = false;
                        }
                        else if (c == '+' && afterexp)
                        {
                            afterexp = false;
                        }
                        else
                        {
                            afterexp = false;

                            if (!digit)
                            {
                                if (point && start == iIndex - 1)
                                {
                                    sToken = ".";
                                    iType = TokenType.SPECIAL;

                                    return true;
                                }

                                throw new Exception("unexpected token. " + c);
                            }

                            sToken = sCommand.Substring(start, (iIndex - start));

                            return true;
                        }
                        break;
                }

                iIndex++;
            }
        }

        /// <summary>
        /// 当前输入是否符合指定字符串
        /// </summary>
        /// <param name="match"></param>
        /// <returns></returns>
        public bool IsMatch(string match)
        {
            return sToken.Equals(match);
        }

        /// <summary>
        /// 是否数字、bool型
        /// </summary>
        public bool IsNumeric
        {
            get
            {
                if (iType == TokenType.STRING ||
                    iType == TokenType.NUMBER ||
                    iType == TokenType.FLOAT)
                {
                    return true;
                }

                if (sToken.Equals("NULL"))
                {
                    return true;
                }

                if (sToken.Equals("TRUE") || sToken.Equals("FALSE"))
                {
                    return true;
                }

                return false;
            }
        }

        /// <summary>
        /// 是否长字符串 table.column
        /// </summary>
        public bool IsLongField
        {
            get
            {
                return iType == TokenType.LONG_NAME;
            }
        }

        /// <summary>
        /// 是否变量
        /// </summary>
        public bool IsVariable
        {
            get
            {
                return iType == TokenType.VARIABLE;
            }
        }

        /// <summary>
        /// 是否字符串 不包括关键词
        /// </summary>
        public bool IsField
        {
            get
            {
                if (iType == TokenType.QUOTED_IDENTIFIER)
                {
                    return true;
                }

                if (iType != TokenType.NAME)
                {
                    return false;
                }

                return !hKeyword.Contains(sToken);
            }
        }

        /// <summary>
        /// 长名前缀
        /// </summary>
        public string LongFieldFirst
        {
            get
            {
                return sLongNameFirst;
            }
        }

        /// <summary>
        /// 长名后缀
        /// </summary>
        public string LongFieldLast
        {
            get
            {
                return sLongNameLast;
            }
        }

        /// <summary>
        /// 获取字符串 包括关键词
        /// </summary>
        /// <returns></returns>
        public string GetField()
        {
            if (!IsField)
            {
                throw new Exception("unexpected token. " + sToken);
            }

            return sToken;
        }

        /// <summary>
        /// 取得当前实际token值
        /// </summary>
        /// <returns></returns>
        public string GetToken()
        {
            return sToken;
        }

        /// <summary>
        /// 当前token类型
        /// </summary>
        public TokenType TokenType
        {
            get
            {
                return iType;
            }
        }

        /// <summary>
        /// 取得token的值
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public T GetToken<T>()
        {
            object rvalue = null;

            if (!IsNumeric)
            {
                throw new Exception("unexpected token. " + sToken);
            }

            if (iType == TokenType.STRING)
            {
                rvalue = sToken;

                return (T)rvalue;
            }

            // convert NULL to null string if not a string
            // todo: make this more straightforward
            if (sToken.Equals("NULL"))
            {
                return default(T);
            }

            if (iType == TokenType.NUMBER)
            {
                if (sToken.Length > 9)
                {

                    // 2147483647 is the biggest int value, so more than
                    // 9 digits are better returend as a long
                    iType = TokenType.LONG;

                    rvalue = long.Parse(sToken);

                    return (T)rvalue;
                }

                rvalue = int.Parse(sToken);
            }
            else if (iType == TokenType.FLOAT)
            {
                rvalue = Double.Parse(sToken);

                return (T)rvalue;
            }

            rvalue = sToken;

            return (T)rvalue;
        }

        /// <summary>
        /// 取得当前的内容
        /// </summary>
        public int Position
        {
            get
            {
                return iIndex;
            }
        }

        /// <summary>
        /// 取得范围内的字段
        /// </summary>
        /// <param name="begin"></param>
        /// <param name="end"></param>
        /// <returns></returns>
        public string GetPart(int begin, int end)
        {
            return sCommand.Substring(begin, (end - begin));
        }
    }

    public enum TokenType
    {
        /// <summary>
        /// 大部分都是name
        /// </summary>
        NAME = 1,
        /// <summary>
        /// 带。的名字，例如table.column
        /// </summary>
        LONG_NAME = 2,
        /// <summary>
        /// 特殊字段，包括(),*=;+% / !<>|/-
        /// </summary>
        SPECIAL = 3,
        /// <summary>
        /// 数字 不包含字母
        /// </summary>
        NUMBER = 4,
        /// <summary>
        /// 数字，当带小数点并且没有字符
        /// </summary>
        FLOAT = 5,
        /// <summary>
        /// 单引号内的字符串
        /// </summary>
        STRING = 6,
        /// <summary>
        /// 如果数字长度>9，则是long型
        /// </summary>
        LONG = 7,
        /// <summary>
        /// 变量 例如 :TABLENAME
        /// </summary>
        VARIABLE = 8,
        /// <summary>
        /// 引用的字段，在双引号内
        /// </summary>
        QUOTED_IDENTIFIER = 9,
        /// <summary>
        /// 注释
        /// </summary>
        REMARK_LINE = 10,
        /// <summary>
        /// 注释
        /// </summary>
        REMARK = 11
    }
}
